using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using mebc.reflection;

namespace mebc
{
    public interface IBoard : IFunctionalUnit
    {
        void RedirectInputTo<T>(IFunctionalUnit consumer);
        void CaptureOutputFrom<T>(IFunctionalUnit producer);
        void WireFromTo<T>(IFunctionalUnit from, IFunctionalUnit to);
    }


    public class Board : FunctionalUnit, IBoard
    {
        private readonly Dictionary<Type, IFunctionalUnit> _inputRedirections = new Dictionary<Type, IFunctionalUnit>();
        private readonly Dictionary<Tuple<IFunctionalUnit, Type>, IFunctionalUnit> _wires = new Dictionary<Tuple<IFunctionalUnit, Type>, IFunctionalUnit>();
        private readonly HashSet<Tuple<IFunctionalUnit, Type>> _outputCaptures = new HashSet<Tuple<IFunctionalUnit, Type>>();

        public override void InputPipe(IMessage message)
        {
            if (_inputRedirections.ContainsKey(message.MessageType))
                _inputRedirections[message.MessageType].InputPipe(message);
            else
                throw new InvalidCastException("Missing input processor for type: " + message.MessageType.FullName);
        }


        public void RedirectInputTo<T>(IFunctionalUnit consumer)
        {
            _inputRedirections.Add(typeof(T), consumer);
        }


        public void CaptureOutputFrom<T>(IFunctionalUnit producer)
        {
            _outputCaptures.Add(new Tuple<IFunctionalUnit, Type>(producer, typeof(T)));
            producer.OutputPipe += message =>
                                       {
                                           var source = new Tuple<IFunctionalUnit, Type>(producer, message.MessageType);
                                           if (_outputCaptures.Contains(source)) SendMessage(message);
                                       };
        }


        public void WireFromTo<T>(IFunctionalUnit from, IFunctionalUnit to)
        {
            _wires.Add(new Tuple<IFunctionalUnit, Type>(from, typeof (T)), to);
            from.OutputPipe += message =>
                                   {
                                       var source = new Tuple<IFunctionalUnit, Type>(from, message.MessageType);
                                       if (_wires.ContainsKey(source)) _wires[source].InputPipe(message);
                                   };
        }


        public override MetaData GetMetaData()
        {
            var md = new MetaData
                         {
                             Id = this.GetHashCode().ToString(),
                             Type = this.GetType(),
                             InputPins = _inputRedirections.Keys,
                             OutputPins = _outputCaptures.Select(t => t.Item2)
                         };

            var wires = _inputRedirections.Select(redirection => new Wire {Type = redirection.Key, To = redirection.Value.GetMetaData()}).ToList();
            wires.AddRange(_outputCaptures.Select(capture => new Wire {Type = capture.Item2, From = capture.Item1.GetMetaData()}));
            wires.AddRange(_wires.Select(wire => new Wire {Type = wire.Key.Item2, From = wire.Key.Item1.GetMetaData(), To = wire.Value.GetMetaData()}));
            md.Wires = wires;

            return md;
        }
    }
}